package edu.hd4ic.algorithm.realizations;

import edu.hd4ic.algorithm.base.AbstractHDBasedAlgorithm;
import edu.hd4ic.algorithm.base.HDValue;
import edu.hd4ic.edu.hd4ic.data.ProcessingImageDataContainer;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: morfant
 * Date: 08.01.13
 * Time: 17:06
 * To change this template use File | Settings | File Templates.
 */
public class GrayImageHausdorffDistance extends AbstractHDBasedAlgorithm {

    private static final int WINDOW_SIZE = 3;

    private int[] transformBinaryImageToGrayImage(int[] image, int width, int height) {
        int[] grayImage = new int[width * height];
        int i, j;
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                grayImage[i * width + j] = 0;
            }
        }

        int widthMinusOne = width - 1;
        int heightMinusOne = height - 1;
        int m, n, windowHeightBorder, windowWidthBorder, value;


        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                if (image[i * width + j] > 0) {
                    value = 0;
                    if (i >= 1 && j >= 1 && i < heightMinusOne && j < widthMinusOne) {
                        windowHeightBorder = i + 1;
                        windowWidthBorder = j + 1;
                        for (m = i - 1; m <= windowHeightBorder; m++) {
                            for (n = j - 1; n <= windowWidthBorder; n++) {
                                if (((n != j) && (m != i))) {
                                    if (image[m * width + n] > 0) {
                                        value++;
                                    }
                                }
                            }
                        }
                    } else {
                        windowHeightBorder = i + 1;
                        windowWidthBorder = j + 1;
                        for (m = i - 1; m <= windowHeightBorder; m++) {
                            for (n = j - 1; n <= windowWidthBorder; n++) {
                                if (((n != j) && (m != i))) {
                                    if (n >= 0 && m >= 0 && m < height && n < width) {
                                        if (image[m * width + n] > 0) {
                                            value++;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    grayImage[i * width + j] = value;
                }
            }
        }
        return grayImage;
    }

    public List<HDValue> compareImage(ProcessingImageDataContainer firstImageContainer,
                                      ProcessingImageDataContainer secondImageContainer,
                                      double calculationThreshold) {
        Image firstImage = firstImageContainer.getEdgeInputImage() != null ?
                firstImageContainer.getEdgeInputImage() :
                firstImageContainer.getInputImage();
        Image secondImage = secondImageContainer.getEdgeInputImage() != null ?
                secondImageContainer.getEdgeInputImage() :
                secondImageContainer.getInputImage();
        Raster firstImageRaster = ((BufferedImage) firstImage).getData();
        int firstImageRasterWidth = firstImageRaster.getWidth();
        int firstImageRasterHeight = firstImageRaster.getHeight();
        Raster secondImageRaster = ((BufferedImage) secondImage).getData();
        int secondImageRasterWidth = secondImageRaster.getWidth();
        int secondImageRasterHeight = secondImageRaster.getHeight();
        final int BAND_NUMBER = 0;
        BufferedImage modifiedFirstImage = new BufferedImage(firstImageRasterWidth, firstImageRasterHeight, BufferedImage.TYPE_BYTE_GRAY);
        BufferedImage modifiedSecondImage = new BufferedImage(secondImageRasterWidth, secondImageRasterHeight, BufferedImage.TYPE_BYTE_GRAY);
        modifiedFirstImage.getRaster().
                setSamples(0, 0, firstImageRasterWidth, firstImageRasterHeight, BAND_NUMBER,
                        transformBinaryImageToGrayImage(
                                firstImageRaster.
                                        getSamples(0, 0, firstImageRasterWidth, firstImageRasterHeight,
                                                BAND_NUMBER, (int[]) null),
                                firstImageRasterWidth,
                                firstImageRasterHeight
                        ));
        modifiedSecondImage.getRaster().
                setSamples(0, 0, secondImageRasterWidth, secondImageRasterHeight, BAND_NUMBER,
                        transformBinaryImageToGrayImage(
                                secondImageRaster.
                                        getSamples(0, 0, secondImageRasterWidth, secondImageRasterHeight,
                                                BAND_NUMBER, (int[]) null),
                                secondImageRasterWidth,
                                secondImageRasterHeight
                        ));
//        try {
//            ImageIO.write(modifiedFirstImage,
//                    "bmp",
//                    new File("first modifided image" + System.currentTimeMillis() + ".bmp"));
//            ImageIO.write(modifiedFirstImage,
//                    "bmp",
//                    new File("second modifided image" + System.currentTimeMillis() + ".bmp"));
//        } catch (IOException e) {
//            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
//        }
        ProcessingImageDataContainer newFirstImageContainer =
                new ProcessingImageDataContainer(firstImageContainer.getInputImage());
        newFirstImageContainer.setEdgeInputImage(modifiedFirstImage);
        ProcessingImageDataContainer newSecondImageContainer =
                new ProcessingImageDataContainer(secondImageContainer.getInputImage());
        newSecondImageContainer.setEdgeInputImage(modifiedSecondImage);
        return super.compareImage(newFirstImageContainer, newSecondImageContainer, calculationThreshold);
    }

    @Override
    protected double calculateDirectDistance(int[] firstImageDataBlock, int[] secondImageDataBlock, int width, int height) {
//        int[] grayFirstImageDataBlock = transformBinaryImageToGrayImage(firstImageDataBlock, width, height);
//        int[] graySecondImageDataBlock = transformBinaryImageToGrayImage(secondImageDataBlock, width, height);
        int[] grayFirstImageDataBlock = firstImageDataBlock;
        int[] graySecondImageDataBlock = secondImageDataBlock;
        double maxDistance = 0.0;
        double distanceFromPixelToSet;
        int pixelValue;
        int i, j;

        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                pixelValue = grayFirstImageDataBlock[i * width + j];
                if ((pixelValue > 0) && (pixelValue < 8)) {
                    distanceFromPixelToSet = calculateDistanceFromPixelToSet(pixelValue, i, j, graySecondImageDataBlock, width, height);
                    if (distanceFromPixelToSet > maxDistance) {
                        maxDistance = distanceFromPixelToSet;
//                        if(maxDistance>calculationTreshold){
//                            return maxDistance;
//                        }
                    }
                }
            }
        }

        return maxDistance;
    }

    private double calculateDistanceFromPixelToSet(int searchPixelValue, int x, int y,
                                                   int[] secondImageDataBlock, int width, int height) {
        double distance = 0.0;
//        double minDistance = Double.MAX_VALUE;
        double minDistanceBaseColor = Double.MAX_VALUE;
        int brighterColorPixelValue = searchPixelValue + 1;
        double minDistanceBrighteColor = Double.MAX_VALUE;
        int darkerColorPixelValue = searchPixelValue - 1;
        double minDistanceDarkerColor = Double.MAX_VALUE;

        int searchIndent = 0;
        int cochleaLenght;
        int pixelValue;

        int i, j, destinationX, destinationY;

        boolean isFound = false;

        while (!isFound) {
            cochleaLenght = 2 * searchIndent + 1;
            for (i = 0; i < cochleaLenght; i++) {

                if ((i == 0) || (i == cochleaLenght - 1)) {//Первая и последняя строки. Интересуют все элементы строки.

                    for (j = 0; j < cochleaLenght; j++) {

                        destinationX = x - searchIndent + i;
                        destinationY = y - searchIndent + j;

                        if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                            pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                            if ((pixelValue >= darkerColorPixelValue) && (pixelValue <= brighterColorPixelValue)) {

                                isFound = true;
                                distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                                if (pixelValue == searchPixelValue) {
                                    if (minDistanceBaseColor > distance) {
                                        minDistanceBaseColor = distance;
                                    }
                                }
                                if (pixelValue == brighterColorPixelValue) {
                                    if (minDistanceBrighteColor > distance) {
                                        minDistanceBrighteColor = distance;
                                    }
                                }
                                if (pixelValue == darkerColorPixelValue) {
                                    if (minDistanceDarkerColor > distance) {
                                        minDistanceDarkerColor = distance;
                                    }
                                }
                            }
                        }

                    }
                } else {//Промежуточные строки - когда нас интересуют только первый и последний элемент строки.
                    destinationX = x - searchIndent + i;
                    destinationY = y - searchIndent;

                    if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                        pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                        if ((pixelValue >= darkerColorPixelValue) && (pixelValue <= brighterColorPixelValue)) {

                            isFound = true;
                            distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                            if (pixelValue == searchPixelValue) {
                                if (minDistanceBaseColor > distance) {
                                    minDistanceBaseColor = distance;
                                }
                            }
                            if (pixelValue == brighterColorPixelValue) {
                                if (minDistanceBrighteColor > distance) {
                                    minDistanceBrighteColor = distance;
                                }
                            }
                            if (pixelValue == darkerColorPixelValue) {
                                if (minDistanceDarkerColor > distance) {
                                    minDistanceDarkerColor = distance;
                                }
                            }
                        }
                    }

                    destinationX = x - searchIndent + i;
                    destinationY = y + searchIndent;

                    if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                        pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                        if ((pixelValue >= darkerColorPixelValue) && (pixelValue <= brighterColorPixelValue)) {

                            isFound = true;
                            distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                            if (pixelValue == searchPixelValue) {
                                if (minDistanceBaseColor > distance) {
                                    minDistanceBaseColor = distance;
                                }
                            }
                            if (pixelValue == brighterColorPixelValue) {
                                if (minDistanceBrighteColor > distance) {
                                    minDistanceBrighteColor = distance;
                                }
                            }
                            if (pixelValue == darkerColorPixelValue) {
                                if (minDistanceDarkerColor > distance) {
                                    minDistanceDarkerColor = distance;
                                }
                            }
                        }
                    }
                }
            }
            if (!isFound) {
                searchIndent++;
                if (!checkThatWorkIsDoingWithinImage(x, y, searchIndent, width, height)) {
//                    throw new RuntimeException("Полный выход за пределы изображения - одно из изображений пусто.");
                    return Double.MAX_VALUE;
                }
            }
        }

        return Math.min(Math.min(minDistanceBaseColor, minDistanceBrighteColor), minDistanceDarkerColor);
    }

    private boolean checkThatWorkIsDoingWithinImage(int x, int y, int indent, int widht, int height) {
        return (x - indent >= 0)//Вверх изображения
                || (y - indent >= 0)//Левая часть изображения
                || (x + indent < widht)//Низ изображения
                || (y + indent < height);//Правая часть изображения
    }

    private boolean checkThatPositionIsWithinImage(int x, int y, int widht, int height) {
        return (x >= 0)
                && (y >= 0)
                && (x < widht)
                && (y < height);
    }

    private double calculateEuclideanSpace(int beginX, int beginY, int endX, int endY) {
        return Math.sqrt(Math.pow(beginX - endX, 2) + Math.pow(beginY - endY, 2));
    }
}
